package com.ax.framework.jfinal.db

import cn.hutool.log.LogFactory
import com.alibaba.fastjson.JSONArray
import com.alibaba.fastjson.JSONObject
import com.alibaba.fastjson.serializer.SerializeConfig
import com.ax.framework.jfinal.db.dialect.PostgreSqlDialect
import com.ax.framework.jfinal.engine.PDirective
import com.ax.getArray
import com.ax.getClassesByPackageName
import com.jfinal.kit.PathKit
import com.jfinal.plugin.activerecord.ActiveRecordPlugin
import com.jfinal.plugin.activerecord.Record
import com.jfinal.plugin.activerecord.dialect.SqlServerDialect
import com.jfinal.plugin.activerecord.generator.Generator
import com.jfinal.plugin.druid.DruidPlugin
import com.jfinal.template.source.ClassPathSourceFactory
import org.yaml.snakeyaml.Yaml
import java.io.InputStream
import java.lang.Exception


/**
 * 此类为JFinalArpPlugin的一个包装,
 * 接受固定格式参数, 初始化ActiveRecordPlugin
 * 目前方言写死pg, 将来再适配
 *
 *@author  张天笑
 *@datetime  2018/11/22 0022 14:04
 *
 * */

private val logger = LogFactory.get(Arp::class.java)

object Arp {

    /**
     *@author  小杨
     *@datetime  2018/11/22 0022 14:05
     *
     * @param arp_cofig_stream 数据源配置文件输入流
     * */
    fun init(arp_cofig_stream: InputStream): List<ActiveRecordPlugin> {
        //转bean
        val data_sources_bean = Yaml().loadAs(arp_cofig_stream, DataSourcesBean::class.java)

        //初始化数据源
        return initDataSource(data_sources_bean.getValue())
    }

    fun start(json_array: JSONArray): List<ActiveRecordPlugin> {
        return initDataSource(json_array.toList() as List<JSONObject>)
    }

    private fun initDataSource(json_object_list: List<JSONObject>): List<ActiveRecordPlugin> {
        val enabled_config_list = json_object_list.filter { it.getBoolean("enable") } //过滤掉未启用的配置

        //数据源id不能相同
        val arp_id_list_size = enabled_config_list.map {
            val arp_id = it.getString("id")
            if (arp_id == null || arp_id == "") {
                Exception("数据源id不能为空")
                return@map
            }
        }.toSet().size

        if (arp_id_list_size != enabled_config_list.size) {
            throw Exception("存在重复的 arp_id!")
        }

        return enabled_config_list.map {

            val dp = getDruidPlugin(it) //druid_plugin
            dp.start()

            val arp_id = it.getString("id")
            if (arp_id == null || arp_id == "") {
                logger.error("数据源id不能为空")
            }
            val arp = ActiveRecordPlugin(arp_id, dp) //arp_plugin

            //设置方言,
            it.getString("url").let {
                if (it.startsWith("jdbc:postgresql")) {
                    arp.setDialect(PostgreSqlDialect)
                } else if (it.startsWith("jdbc:sqlserver")) {
                    arp.setDialect(SqlServerDialect())
                } else {

                }
            }

            arp.setDevMode(it.getBoolean("devMode")) //是否为开发模式, 非开发模式会缓存sql_engine读取到的sql模板id, 一定程度提高效率

            val mapping = it.getBoolean("mapping")
            //不设置 ,或者为 true 时  , 添加表 和 model的 映射
            if (mapping == null || mapping) {
                val threadClazz = Class.forName("${it.getString("model_package_name")}._MappingKit") //添加 table 和 model的 映射
                val method = threadClazz.getMethod("mapping", ActiveRecordPlugin::class.java) //
                method.invoke(null, arp)
            }


            arp.setShowSql(it.getBoolean("showsql")) //是否打印sql, 无参数

            val serializeConfig = SerializeConfig.getGlobalInstance() //FastJson序列化全局配置对象

            serializeConfig.put(Record::class.java, JFinalRecordSerializer()) //配置JFinalRecord转JSON字符串规则

            val model_package_name = it.getString("model_package_name")
            if (model_package_name == null || model_package_name == "") {
                logger.info("")
            } else {
                model_package_name.getClassesByPackageName().forEach { it ->
                    // 遍历model包下所有类, 排除BaseModel, 配置 JFInalModel转JSON字符串规则
                    val superclass = it.getGenericSuperclass()
                    if (superclass != null) {
                        val typeName = superclass.getTypeName()
                        //排除Base
                        if (typeName.contains("Base")) {
                            serializeConfig.put(it, JFinalModelSerializer())
                        }
                    }
                }
            }


            val sql_engine = arp.sqlKit.engine //每一个arp对象都有自己的sql engine
                    .setSourceFactory(ClassPathSourceFactory())  //需要sql engine管理sql文件时, 告诉它文件从哪里获取
                    .removeDirective("p").addDirective("p", PDirective::class.java) // 自定义p指令

            //模板共享对象
            /*it.getJSONObject("share_objects")?.also {
                it.forEach { et ->
                    sql_engine.addSharedObject(et.key, Class.forName(et.value.toString()).newInstance())
                }
            }*/

            //sql模板管理
            it.getString("sql_template")?.also {
                arp.addSqlTemplate(it)
            }

            // arp.sqlKit.parseSqlTemplate()
            arp.start()

            println("====  数据源${arp.config.name} 初始化完毕 ==== ")

            arp
        }
    }


    /**
     * 生成Model
     *@author  小杨
     *@datetime  2018/11/22 0022 16:38
     *
     * */
    fun generic(arp_cofig_stream: InputStream) {
        //转bean
        val data_sources_bean = Yaml().loadAs(arp_cofig_stream, DataSourcesBean::class.java)

        generic(data_sources_bean.getValue())
    }

    fun generic(json_array: List<JSONObject>) {
        json_array.filter { it.getBoolean("enable") }.forEach {

            //获取DbRecord所需参数
            val modelPackageName = it.getString("model_package_name")  // com.ax.jshop.model 所使用的包名 (MappingKit 默认使用的包名)

            val baseModelPackageName = modelPackageName + ".base" // base com.ax.jshop.model 所使用的包名

            val out_put_root = PathKit.getWebRootPath() + "\\" + (it.get("out_put_root") ?: "")

            val baseModelOutputDir = "$out_put_root/src/main/java/${baseModelPackageName.replace(".", "/")}" // maven项目的  base_model_output_dir

            //val baseModelOutputDir = PathKit.getWebRootPath() + it.getString("base_model_output_dir") // base com.ax.jshop.model 文件保存绝对路径
            val modelOutputDir = baseModelOutputDir + "/.."  // maven项目的  model_output_dir, (MappingKit 与 DataDictionary 文件默认保存路径)

            //加载把并启动德鲁伊插件 ,这里设置初始化连接数为1
            it["initial_size"] = 1
            val druidPlugin = getDruidPlugin(it)
            druidPlugin.start()

            //获取连接池
            val ds = druidPlugin.getDataSource()

            // 创建生成器
            val gen = Generator(ds, baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir)

            //
            val mb = MetaBuilder(ds)
            // 设置需要被移除的表名前缀用于生成modelName。例如表名 "osc_user"，移除前缀 "osc_"后生成的model名为 "User"而非 OscUser
            val tablePrefix = it.getString("table_prefix")
            mb.setRemovedTableNamePrefixes(tablePrefix)
            //
            gen.setMetaBuilder(mb)

            //
            gen.setMappingKitPackageName(modelPackageName)

            //指定model模板路径
            gen.setBaseModelTemplate(it.getString("base_model_template_path"))

            // 设置数据库方言
            gen.setDialect(PostgreSqlDialect)

            // 添加不需要生成的表名
            val toTypedArray = it.getArray<String>("excluded_tables")
            gen.addExcludedTable(*toTypedArray)

            // 设置是否在 Model 中生成 dao 对象
            gen.setGenerateDaoInModel(true)

            // 设置是否生成字典文件
            gen.setGenerateDataDictionary(true)

            //允许model setter返回this
            gen.setGenerateChainSetter(true)

            // 生成
            gen.generate()
        }
    }

    fun generic(json_array: JSONArray) {
        generic(json_array.toList() as List<JSONObject>)
    }

    //简单抽取, 在arp和generic中复用代码
    private fun getDruidPlugin(it: JSONObject): DruidPlugin {
        val url = it.getString("url")
        val username = it.getString("username")
        val password = it.getString("password")

        //加载德鲁伊插件
        val dp = DruidPlugin(url, username, password)

        //设置最大连接数与初始化连接数
        val max_active = it.getIntValue("max_active")
        val initial_size = it.getIntValue("initial_size")
        val min_idle = it.getIntValue("min_idle")

        dp.setMaxActive(max_active)
        dp.setInitialSize(initial_size)
        dp.setMinIdle(min_idle)
        // 加强数据库安全,防御SQL注入攻击
        // 由于insert <- select语法和 returning id 两处都被wf拦截掉, 弃用之
        /*val wallFilter = WallFilter();
        wallFilter.setDbType(JdbcConstants.ORACLE);
        druidPlugin.addFilter(wallFilter);*/

        // 添加 StatFilter 才会有统计数据, sql统计我用pg_stat, sql, 执行次数, 块命中率等
        //druidPlugin.addFilter(StatFilter())
        // druidPlugin.setLogAbandoned(true)
        return dp
    }


}